C#核心

面向对象编程概念

面向过程编程是一种以过程为中心的编程思想。

面向对象编程是一种对现实世界理解和抽象的编程方式,把相关的数据和方法组织成一个整体来看待,从更高层次来进行程序开发。更贴近事物的自然运行模式。

用通俗易懂的大白话说就是万物皆为对象:用程序来抽象形容对象,用面向对象的思想来编程

为什么要学习面向对象编程

  • 提高代码复用率
  • 提高开发效率
  • 提高程序可拓展性
  • 清晰的逻辑关系

面向对象三大特征

学习 (class)

封装 + 继承 + 多态

  • 封装:用程序语言来形容对象。
  • 继承:复用封装对象的代码;儿子继承父亲,复用现成代码
  • 多态;同样行为的不同表现,儿子继承父亲的基因但是有不同的行为表现。

面向对象七大原则

开闭原则、依赖倒转原则、里氏替换原则、单一职责原则、接口隔离原则、合成复用原则、迪米特法则。

面向对象——封装

类和对象

什么是类

具有相同特征,具有相同行为,一类事物的抽象,类是对象的模板,关键词:class

声明类

一般申明再namespace语句块里。

1
2
3
4
5
6
7
8
9
10
class xxx
{
//特征——成员变量
//行为——成员方法
//保护特征——成员属性
//构造函数和析构函数
//索引器
//运算符重载
//静态成员
}

类对象

类的声明和类对象(变量)的声明式两个概念。

类的声明类似枚举和结构体声明,相当于是声明了一个自定义变量的类型。
而对象是类创建出来的,相当于声明了一个指定类的的变量,类创建对象的过程,一般称之为实例化对象。类对象都是引用类型的

实例化对象:

1
2
3
类名 变量名
类名 变量名 = null
类名 变量名 = new 类名()

成员变量和访问修饰符

成员变量

成员变量必须声明在类语句块中,用来描述对象的特征,可以是任意的变量类型,数量不做限制。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum E_Sex
{
Man,
Woman,
}
class Person
{
//特征——成员变量
string name = "宇智波带土"
int age;
E_Sex sex;
Person girlFriend;
Person[] Friend;
}

如何使用成员变量

1
2
Person p = new Person();
p.xxx = xxx;

访问修饰符

  • public —— 共有的,自己和别人都能访问
  • private —— 私有的,自己内部才能访问,不写默认为private
  • protected —— 保护的,自己和子类能访问

成员方法

声明在类语句块里,用来描述对象行为的,受到访问修饰符的影响,返回值参数不做限制。

注意:
成员方法不要加static关键字
成员方法必须实例化出对象,再通过对象来使用,相当于对象执行了某个行为
成员方法受到访问修饰符影响

成员方法的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Person
{
public void Speak(string str)
{
Console.WriteLine("{0}说{1}", name, string)
}
public bool IsAdult
{
return age >= 18;
}
public AddFriend(Person p)
{
if(friends == null)
{
friends = new Person[] { p };
}
else
{
Person[] newFriends = new Person[friends.Length + 1];
for(int i = 0; i < friends.Length; i++)
{
newFriends[i] = friends[i];
}
newFriends[newFriends.Length - 1] = p;
friends = newFriends;
}
}
public string name;
public int age;
public Person[] friends;
}

Person p = new Person();
p.name = "带土"
p.age = 18;
p.Speak("过家家就到此为止了")
if(p.IsAdult)
{
p.Speak("通灵术,外道魔像!")
}
person p2 = new Person();
p2.name = "旗木卡卡西"
p2.age = 18;
p.AddFriend(p2);

构造、析构、垃圾回收

构造函数

在实例化对象时会调用的用于初始化的函数,如果不写,默认存在一个无参构造函数。

构造函数的写法:

  • 没有返回值
  • 函数名和类名必须相同
  • 没有特殊需求时一般都是public
  • 构造函数可以被重载
  • this代表当前调用该函数的对象自己
1
2
3
4
5
6
7
8
9
10
11
12
class Person
{
int age;
string name;
public Person(int age, string name)
{
this.age = age;
this.name = name;
}
}

Person p = new Person(18, "宇智波带土");

注意类中是允许自己申明无参构造函数的,如果自己不实现无参构造函数而直接实现有参构造函数,会失去默认的无参构造。

构造函数特殊写法:可以通过this重用构造函数代码

1
2
3
4
public Person(int age, string name): this()
{
会先调用this里面的构造函数,然后再进入该构造函数中
}

析构函数

当引用类型的堆内存被回收时,会调用该函数,用法为:

1
2
3
4
~Person()
{

}

当垃圾真正被回收的时候才会调用。

垃圾回收

垃圾回收的过程是在遍历堆(Heap)上动态分配的所有对象,通过识别他们是否被引用来确定哪些对象是垃圾,哪些对象仍要被使用,所谓的垃圾就是没有被任何变量、对象引用的内容

垃圾回收有很多种算法

  • 引用计数(Reference Counting)
  • 标记清理(Mark Sweep)
  • 标记整理(Mark Compact)
  • 复制集合(copy collection)

注意:垃圾回收只负责堆内存的垃圾回收,引用类型都是存在堆内存中的,因此它的分配和释放都通过垃圾回收机制来管理。

在c#中内存回收会分成三代内存:0代内存、1代内存、2代内存
代是垃圾回收机制使用的一种算法,新分配的对象会被配置在第0代内存中,每次分配都可能会进行垃圾回收以释放内存。

在一次内存回收过程开始时,垃圾回收器会认为堆中全是垃圾,会进行:
1、标记对象,从根开始检查引用对象,标记后为可达对象,未标记为不可达对象(垃圾)
2、搬迁对象压缩堆,释放未标记对象,搬迁可达对象,修改引用地址

大对象总被认为是第二代内存,目的是减少性能损耗,提高性能。

手动执行垃圾分类(GC):

1
GC.Collect(); 

成员属性

用于保护成员变量,为成员属性的获取和赋值添加逻辑处理,并且可以解决3p的局限性。.

成员属性的使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Person
{
private string name;
private int age;
private int money;
public string Name
{
get
{
这里可以写逻辑
return name;
}
set
{
这里也可以写逻辑
//value表示传入的值
name = value;
}
}
}
Person p = new Person();
p.Name = "宇智波带土";

get和set前面可以加访问修饰符,默认不加,会使用属性声明时的访问权限,家的访问修饰符要低于属性的访问权限,但是不能让get和set的访问权限都低于属性权限。

get和set可以只有一个

自动属性:外部能得不能改的特征。
如果类中有一个特征是只希望外部能得不能改的,又没有什么特殊处理,那么就可以直接使用自动属性。

1
2
3
4
5
public float Height
{
get;
private set;
}

索引器

让对象可以像数组一样通过索引访问其中元素,使程序看起来更直观,更容易编写。

1
2
3
4
5
6
访问修饰符 返回值 this[参数类型 参数名,参数类型 参数名...]
{
内部的写法和规则和属性相同
get{}
set{}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person
{
private string name;
private int age;
private Person[] friends;
public Person this[int idx]
{
get
{
return friends[idx];
}
set
{
friends[idx] = value;
}
}
}
Person p = new Person();
p[0] = new Person();

索引器可以写逻辑(与属性相同),索引器可以重载。

索引器中可以尝试所有参数类型之间的关联,不一定只局限于一种参数。

静态成员

静态关键字:static,用static修饰成员变量、方法、属性等称为静态成员。直接使用类名点出使用。

1
2
3
4
5
6
7
8
class Text
{
public static float PI = 3.1415926f;
public static float CalcCircle(float r)
{
return PI * r * r;
}
}

静态成员无需进实例化,在程序一开始就会开始进行,一直到程序结束时内存空间才会被释放。

静态函数中不能使用非静态成员。但是非静态函数可以使用静态成员

静态成员的作用:
静态变量:
常用于唯一变量的声明。
方便别人获取对象声明
静态方法:
常用唯一的方法声明,比如相同规则的科学数学计算相关函数。

常量和静态变量
常量可以理解成特殊的static。
不同点:

  • const必须初始化,不能修改,static没有这个规则
  • const只能修饰变量,static可以修饰很多
  • const一定写在访问修饰符后面,static没有这个要求

静态类和静态构造函数

静态类

用static修饰的类。只能包含静态成员,不能被实例化。
作用:

  • 将常用的静态成员写在静态类中,方便使用
  • 静态类不能被实例化,更能体现工具类对的唯一性
1
2
3
4
5
6
7
8
static class Text
{
public static int x = 0;
public static void run()
{

}
}

静态构造函数

在构造函数前加上static修饰
特点:

  • 静态类和普通类都可以有。
  • 不可以使用访问修饰符。
  • 不能有参数。
  • 只会自动调用一次。
    作用:在静态构造函数中初始化静态变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static class StaticClass
{
public static int x = 0;
public static int y = 1;
static StaticClass()
{

}
}
class Text
{
public static int x = 0;
public static int y = 1;
static Text()
{

}
}

拓展方法

为现有的非静态变量类型添加新方法
作用:

  • 提高程序拓展性
  • 不需要在对象中重新写方法
  • 不需要继承来添加方法
  • 为别人封装的类型写额外方法
    特点:
  • 一定写在静态类中
  • 一定是个静态函数
  • 第一个参数为拓展目标
  • 第一个参数用this修饰
1
2
3
4
5
6
7
8
9
10
static class Tools
{
//value是使用该方法的实例化对象
public static void Speak(this int value, ...)
{
xxxx
}
}
int i = 10;
i.Speak(...);

如果拓展方法与原方法的名称重合了,那么调用时使用的是原方法

运算符重载

让自定义类和结构体能够使用运算符,使用关键字operator
特点:

  • 一定是一个公共的静态方法
  • 返回值写在operator前面
  • 逻辑处理自定义
    作用:让自定义类和结构体对象可以进行运算
    注意:
  • 条件运算符需要成对实现
  • 一个符号可以多个重载
  • 不能使用ref和out
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Point
{
public int x;
public int y;
public static Point operator +(Point p1, Point p2)
{
Point p = new Point();
p.x = p1.x + p2.x;
p.y = p1.y + p2.y;
return p;
}
}
Point p = new Point();
p.x = 1;
p.y = 1;
Point p2 = new Point();
p2.x = 2;
p2.y = 2;
Point p3 = new Point();
p3 = p + p2;

++,–也可以重载,只传一个参数。
!也可重载,只传一个参数
位运算符均可重载,其中取反只传一个参数

不可重载的运算符:

  • &&,||
  • [ ]
  • ( )
  • 三目运算符
  • 赋值号=

内部类和分部类

内部类

在一个类中再声明一个类,使用时用包裹着 . 出自己,亲密关系的变现。
注意,访问修饰符的作用很大。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person
{
public int age;
public Body body;
public class Body
{
Arm leftArm;
Arm rightArm;
public class Arm
{

}
}
}
Person p = new Person();
Person.Body body = new Person.Body();
Person.Body.Arm arm = new Person.Body.Arm()

分部类

把一个类分成几部分来声明,关键字:partial。
作用:分部描述一个类,增加程序的拓展性
注意:分部类可以写在多个脚本文件中,分部类的访问修饰符要一致,分部类中不能有重复成员。

1
2
3
4
5
6
7
8
9
10
partial class Student
{
public bool sex;
public string name;
}
partial class Student
{
public int number;

}

分布方法

将方法的声明和实现分离
特点:

  • 不能加访问修饰符,默认就是私有
  • 只能在分部类中声明
  • 返回值只能是void
  • 可以有参数但不用out关键字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
partial class Student
{
public bool sex;
public string name;

partial void Speak(string str);
}
partial class Student
{
public int number;
partial void Speak(string str)
{
...
}
}

面向对象——继承

继承的基本规则

一个类A继承一个类B,A类会继承B类的所有成员,A类将拥有B的所有特征行为。

被继承的类:父类,基类,超类。
继承的类:子类,派生类。

子类可以有自己的特征和行为。

特点:

  • 单根性:子类只能有一个父类。
  • 传递性:子类可以间接继承父类的父类。

用法:

1
2
3
4
class 类名 : 被继承类名
{

}

访问修饰符protected可以使得内部和子类都可以使用,但是外部不允许使用。

c#中允许存在子类和父类同名的成员,但是不建议使用

里氏替换原则

是面向对象七大原则中的最重要的原则,
任何父类出现的地方,子类都可以继承。

重点:语法表现——父类容器装子类容器,因为子类对象包含了父类的所有内容。
作用:方便进行对象存储和管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class GameObject
{

}
class Player : GameObject
{
public void PlayerAtk()
{

}
}
class Monster : GameObject
{
public void MonsterAtk
{

}
}
class Boos : GameObject
{
public void BossAtk
{

}
}

GameObject player = new Player();
GmaeObject boss = new Boss();
//用父类容器装载子类对象

is 和 as

is:判断一个对象是否是执行类对象
返回值:bool。
as:将一个对象转换为指定类对象
返回值:指定类对象

1
2
3
4
5
6
if(player is Player())
{
Player p = player as Player;
p.PlayerAtk();
(player as Player).PlayerAtk();
}

继承中的构造函数

特点:当声明一个子类对象时,先执行父类构造函数再执行子类构造函数
注意:父类无参构造很重要,子类可以通过base关键字代表父类调用父类构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class GameObject
{
public GameObject()
{

}
}
class Player : GameObject
{
public Player()
{

}
}

子类实例化时,默认自动调用父类无参构造,所以如果父类无参被顶掉,会报错。

如何做到不执行父类无参构造函数而执行父类有参构造呢。
我们可以使用base

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class GameObject
{
public GameObject(int i)
{

}
}
class Player : GameObject
{
public Player(int i) : base(i)
{

}
}

万物之父和装箱拆箱

万物之父

关键字object,是所有类型的基类,它是一个类。
作用:

  • 可以利用里氏替换原则,用object容器装所有对象
  • 可以用来表示不确定类型,作为函数参数类型。
1
2
3
4
5
6
7
8
9
10
11
object o = new Son();
if(o is Son)
{
(o as Son).Speak();
}
object o2 = 1f;
float i = (float)o2;
object str = "123456";
string str2 = str.ToString();
object arr = new int[10];
int[] ar = arr as int[];

装箱拆箱

发生条件:用object存值类型(装箱),再把object转为值类型(拆箱)。

装箱就是把值类型用引用类型存储,栈内存会迁移到堆内存中。
拆箱就是把引用类型的值类型取出来,堆内存会迁移到栈内存中。

好处:不确定类型时可以方便参数的存储和传递
坏处:存在内存迁移,增加性能消耗

密封类

密封类是使用sealed关键字修饰的类
让类无法再被继承。

作用:
不允许最底层子类被继承,可以保证程序的规范性和安全性。

面向对象——多态

多态vob

多态的概念:多种状态,让继承同一父类的子类们在执行相同方法时有不同的表现
即为让同一对象有唯一行为特征。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Father
{
public void Speak()
{

}
}
class Son : Father
{
public void Speak
{

}
}

函数的重载也是一种多态。

vob:
virtual(虚函数)
override(重写)
base(父类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class GameObject
{
public string name;
public GameObject(string name)
{
this.name = name;
}
public virtual void Atk()
{

}
}
class Player : GameObject
{
public Player(string name) : base(name)
{

}
public override void Atk()
{
base.Atk();//这是继承父类
这里可以重写规则
}
}

抽象类和抽象方法

抽象类

被抽象关键字abstract修饰的类。
特点:

  • 不能被实例化的类
  • 可以包含抽象方法
  • 继承抽象类必须重写抽象方法

抽象类中,封装的所有知识点都可以写在里面。

1
2
3
4
5
6
7
8
abstract class Thing
{
public string name;
}
class Water : Thing
{

}

抽象方法

又叫纯虚方法,用abstract关键字修饰的方法。
特点:

  • 只能在抽象类中声明
  • 没有方法体
  • 不能是私有
  • 继承后必须实现,用override重写
1
2
3
4
5
6
7
8
9
10
11
12
13
abstract class Fruits
{
public string name;
//抽象方法一定不能有方法体
public abstract void Bad();
}
class Apple : Fruits
{
public override void Bad()
{

}
}

当父类中的行为不太需要被实的,只希望子类去定义具体的规则的,可以选择抽象类然后使用其中的抽象方法来定义规则。

接口

接口时行为的抽象规范,关键字interface,不包含成员变量,只包含方法,属性,索引器,事件。成员不能被实现,成员可以不用写访问修饰符,不能是私有的。接口不能继承类,但是可以继承另一个接口。

一个类可以继承多个接口,类继承接口后,必须实现接口中所有成员。

特点:

  • 它和类的声明类似。
  • 接口是用来继承的。
  • 接口不能实例化,但是可以作为容器存储对象。

接口是抽象行为的基类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
interface IFly
{
void Fly();
string Name
{
get;
set;
}
int this[int idx]
{
get;
set;
}
event Action doSomething;
}
class Animal
{

}
class Person : Animal, IFly
{
//也可以加virtual重写
public void Fly()
{

}
public string Name
{
get;
set;
}
public int this[int idx]
{
get
{
return0;
}
set
{

}
}
public event Action doSomething;
}
IFly f = new Person();

接口继承接口时不需要实现

1
2
3
4
5
6
7
8
interface IWalk
{
void Walk();
}
interface IMove : IFly, IWAlk
{

}

显示实现接口:当一个类继承两个接口,但是接口中存在同名方法时
注意:显示实现接口时,不能写访问修饰符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface IAtk
{
void atk();
}
interface ISuperAtk
{
void Atk();
}
class Player : IAtk, ISuperAtk
{
void IAtk.Atk()
{

}
void ISuperAtk.Atk()
{

}
}
Player p = new Player;
(p as IAtk).Atk;
(p as ISuperAtk).Atk;

密封方法

用密封关键字sealed修饰的重写函数
作用:让虚方法或者抽象方法之后不能再被重写,和override一起出现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
abstract class Animal
{
public string name;
public abstract void Eat();
public virtual void Speak()
{

}
}
class Person : Animal
{
public sealed override void Eat()
{

}
public override void Speak()
{

}
}
class WhitePerson : Animal
{
public override void Eat()
{
base.Eat();
}
public override void Speak()
{
base.Speak();
}
}

面向对象相关

命名空间

命名空间是用来组织和重用代码的。
作用:就像是一个工具包,类就像是一件一件工具,都是声明在命名空间中的。

1
2
3
4
5
namespace 命名空间名
{
class...
class...
}

命名空间可以写很多个,并且可以重名。

不同命名空间中相互使用,要引用命名空间或者指明出处。

1
2
3
4
在顶部引用所命名的命名空间,这里举例命名空间为MyGame
using MyGame
也可以指明出处,即为
MyGame.GameObject g = neww MyGame.GameObject();

不同命名空间中允许有同名类,命名空间可以包裹命名空间,命名空间中类前的访问修饰符默认为internal,表示只能在该程序集中使用。

万物之父中的方法

object中的静态方法

Equals 判断两个对象是否相等,最终的判断权交给左边对象的Equals方法,不管是值类型还是引用类型都会按照左侧对象的Equals方法的规则来比较。
ReferenceEquals 比较两个对象是否是相同的引用,主要是用来比较引用类型的对象。值类型对象返回值始终是false。

1
2
3
object.Equals(1,1);
object.ReferenceEquals(t,t2);
这里的t是两个类。

object中的成员方法

普通方法GetType
该方法在反射相关知识点中是十分重要的方法。该方法的主要作用就是获取对象运行时的类型Type,通过Type结合反射相关知识点可以做很多关于对象的操作。
普通方法MemberwiseClone(这是一个保护方法,无法在主函数中直接声明)
该方法用于获取对象的浅拷贝对象,口语化的意思就是会返回一个新的对象,但是新的对象中的引用变量会和老对象一致。

object中的虚方法

虚方法Equals
比较两者是否为同一个引用,相当于上面的ReferenceEquals。但是微软重写了该方法,用来比较值类型是否相等,
虚方法GetHashCode
该方法时获取对象的哈希码
虚方法ToString
该方法是用于返回当前对象代表的字符串,也可以重写转字符串规则,
该方法很常用。当我们调用打印方法时,默认使用的就是对象的ToString方法后打印出来的内容。

String

字符串本质是char数组
转为char数组

1
char[] chars = str.ToCharArray();

字符串拼接

1
str = string.Format("{0}{1}", 1, 333);

正向查找字符的位置,找不到返回-1

1
int idx = str.IndexOf("x");

反向查找字符串位置。

1
int idx = str.LastIndexOf("x");

移除指定位置后的字符

1
2
3
str = str.Remove(1);
从第几个位置已拆除几个字符
str = str.Remove(1,1);

替换指定字符串

1
str = str.Replace("xxx", "yyy");

大小写转换

1
2
str = str.ToUpper();
str = str.ToLower();

字符串截取

1
2
3
4
截取从指定位置开始往后的字符串
str = str.Substring(2);
开始位置和指定个数
str = str.Substring(2, 3);

字符串切割

1
2
string str = "1,2,3,4,5,6,7,8,9";
string[] strs = str.Split(',');

StringBuilder

c#提供的一个用于处理公共字符串的公共类
主要解决的问题是:修改字符串而不是创建新的对象,需要频繁修改和拼接字符串时可以使用它,可以提升性能。
使用前需要引用变量名

1
2
StringBuilder str = new StringBuilder("123123123");
StringBuilder str = new StringBuilder("123123123", 100);

stringbuilder存在一个容量问题,每次往里面加东西都会自动扩容。

获得容量:str.Capacity
获得长度:str.Length

增加:

1
2
str.Append("xxx");
str.AppendFormat("{0}{1}", 100, 999);

插入:

1
str.Insert(0,"xxx");

删:

1
str.Remove(0,10);

清空:

1
str.Clear();

查:

1
str[n];

改:

1
str[0] = 'A';

替换:

1
str.Replace("1","与");

结构体与类的区别

结构体和类最大的区别在储存空间上,因为结构体时值,类时引用。因此他们一个储存在栈上,一个储存在堆上。
结构体和类在使用上很类似,结构体甚至可以用面向对象的思想来形容类对象。
结构体具备面向对象中封装的特征,但是它不具备继承和多态的特征,因此大大减少了它的使用频率。
因为结构体不具备继承特征,因此不能使用protected访问修饰符。
结构体成员变量申明不能指定初始值,类可以。
结构体不能声明无参构造,类可以。
结构体不能声明析构函数,类可以。
结构体需要在构造函数中初始化所有成员变量,类随意。
结构体不能被static修饰,类可以。
结构体不能在内部声明和自己一样的结构体变量,类可以。

结构体可以继承接口,因为接是行为的抽象。

对象是数据集合时,优先考虑结构体,例如坐标、位置。

抽象类与接口的区别

相同点:

  • 都可以被继承
  • 都不能直接实例化
  • 都可以包含方法声明
  • 子类必须实现为实现的方法
  • 都遵循里氏替换原则
    区别:
  • 抽象类中可以有构造函数;接口不能
  • 抽象类中只能被单一继承;接口可以被继承多个
  • 抽象类中可以有成员变量;接口不能有
  • 抽象类中可以声明成员方法;接口只能声明没有实现的抽象方法
  • 抽象类方法可以使用访问修饰符;接口中建议不写。

如何选择:
表示对象用抽象类,表示行为拓展用接口
不同对象拥有的同一种行为,一般用接口来实现